﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Custom;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
using Bling.Angles;

namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.DX;
  public class ParametricWithInference : HasDX10Canvas {
    private static readonly Uri AutumnUri = "Resources/Autumn.jpg".MakePackUri(typeof(ParametricWithInference));
    // These three lines are boiler plate to start and stop the rendering loop as this canvas is active/inactive.
    public ParametricWithInference() : base(true) {
      // first, create some sliders to control the simulation.
      LabelSliderBl Phase = new LabelSliderBl(this) {
        LabelName = "Phase",
        ZIndex = 100,
        LeftTop = Host.LeftBottom,
      };
      LabelSliderBl Frequency = new LabelSliderBl(this) {
        LabelName = "Frequency",
        Minimum = 0,
        Maximum = 40d,
        Value = 10d,
        ZIndex = 100,
        LeftTop = Phase.LeftBottom,
      };
      LabelSliderBl Magnitude = new LabelSliderBl(this) {
        LabelName = "Magnitude",
        Minimum = 0,
        Maximum = 0.5,
        Value = 0.05,
        ZIndex = 100,
        LeftTop = Frequency.LeftBottom,
      };
      // figure out the right size for the host.
      var HostSize = this.Bl().Height - Phase.Height - Frequency.Height - Magnitude.Height;
      Host.Size = HostSize;
      Host.LeftTop = 0d;


      // set host size and position
      // give the overlay some color, otherwise it won't be seen.
      OverlayContent.Background = new SolidColorBrushBl() {
        Color = Colors.Red,
        Opacity = OverlayContent.IsMouseOver.Condition(.02d, .01d),
      };
      // set up the rotation
      QuaternionBl Rotate;
      {
        // An ellipse to represent our virtual trackpad, only visible when the mouse is over the overlay
        EllipseBl sphere = new EllipseBl(OverlayContent) {
          CenterPosition = OverlayContent.CenterSize,
          Stroke = { Brush = Brushes.White, Thickness = 2d },
          // sphere size depends on host size, use 4/5 as a good ratio.
          Size = HostSize * 4d / 5d,
          Visibility = OverlayContent.IsMouseOver,
          // so mouse events on the sphere are passed to overlay content instead
          IsHitTestVisible = false,
        };
        // Use two rotations to more accurately represent transient rotation, rotation A is the current rotation
        // while rotation B is the transient rotation. These are anonymous dependency properties. 
        QuaternionBl RotateA = this.Property(new QuaternionBl(1d.Bl().XXX(), .33.PI()));
        QuaternionBl RotateB = this.Property(QuaternionBl.Identity);
        // we are row-based, so application occurs from right to left, so rotate b has to occur on the left of the multiply
        // sign, just as in matrix compositions. 
        Rotate = RotateB * RotateA;
        Action End;
        {
          Action SetRotateA = QuaternionBl.Assign(RotateA, Rotate);
          Action ResetRotateB = QuaternionBl.Assign(RotateB, QuaternionBl.Identity);
          // end updates the current rotation to the actual rotation and resets the transient rotation to identity. 
          End = () => { SetRotateA(); ResetRotateB(); };
        }
        // compute virtual sphere mouse location according to mouse's 2D position
        Double2Bl MouseAt = sphere.Mouse.Position / sphere.Size;
        // screen cooridnates are [0,1] while 3D coordinates are [-1,1], use n * 2d - 1 to translate.
        MouseAt = MouseAt * 2d - 1d;
        // for some reason, X is flipped in result, easy enough to fix. 
        MouseAt = MouseAt * new Double2Bl(-1d, +1d);
        // determine Z value based on X and Y values, if x,y are outside of the sphere, simply use 0.
        Double3Bl MouseAt3D = new Double3Bl(MouseAt, (1d - MouseAt.LengthSquared).Max(0d).Sqrt);
        // another anonymous dependency property to hold the initial mouse down location.
        Double3Bl MouseInit = this.Property(Double3Bl.Unit);
        // initialize the mouse down location
        Action Init = Double3Bl.Assign(MouseInit, MouseAt3D.Normalize);
        // update transient rotation according to the angle between current mouse location and initial mouse location
        Action Update = QuaternionBl.Assign(RotateB, MouseAt3D.Normalize.AngleBetween(MouseInit));
        // create a drag handler on overlay content (not sphere, since rotation can happen outside of sphere!
        OverlayContent.InstallDragHandler(() => {
          Init();
          return true;
        }, Update, End);
      }
      // world matrix specifies where a render object will go in the world, right now we are just rotated.
      QuaternionBl World = Rotate;
        
      // represents where our eye is in the world.
      Double3Bl Center = new Double3Bl(0, 0, 0);
      //Point3DBl Eye = Center + new Point3DBl((rotateX0.Value + 90).ToDegrees().ToPoint.X * moveZ.Value, 0, (rotateX0.Value + 90).ToDegrees().ToPoint.Y * moveZ.Value);     

      // the 3D cordinate system:
      //  Y
      //  |   Z
      //  |  /
      //  | /
      //  |/
      // O -------- X
      // Configure a WPF perspective camera.
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = Center + 
          new Double3Bl(0,0,-4d),
        //  (/*(RotateEye.Value + 90).ToDegrees().ToPoint*/  -4d.Bl().XX()).InsertY(0),
        Direction = { Look = Center }, FieldOfView = .25.PI().Degrees,
      };

      // Create a sphere displaced by an egg crate height field. 
      // The egg crate's frequency, magnitude, and phase are controlled
      // by WPF sliders.
      Surface Surface = GraphicDefinitions.Sphere.Displace(GraphicDefinitions.EggCrate.Adjust(Frequency, Magnitude, Phase.Value * .25));


      // create a new device in which to hold rendering information
      // Create a texture from the Autumn resource Uri.
      Texture2Bl Autumn = new ImageTexture<D2>(AutumnUri);

      // This code defines how Surface is rendered. It takes a function that provides
      // the vertiex index (n), surface's parametric coordinate (uv), and a vertex result
      // structure (vertex).
      int DIM = 100;
      Device.Render(Surface[DIM,DIM], (Entry, vertex) => {
        // First, specify where the vertex will appear on the screen. The parametric coordinate (uv)
        // allows us to access the vertex's relative position, which we multiple by the world, view,
        // and project matrices.
        //uv = uv.PerVertex();

        vertex.Position = (Camera.Project * Camera.View * World).Transform(Entry.Position); 
        // Compute normal for lighting. Parametric surface normals are computed
        // via symbolic differentiation. Only transform by the world matrix.
        // Must cast to point 3D because lighting computations doesn't work on 4D points,
        // casting will incorporate the w component into the resulting 3D point. 
        //Double3Bl normal = World.Transform(Entry.PerPixel().Normal) * new Double3Bl(1, 1, -1);
        // Compute position for lighting. Again only transform by world matrix.
        //Double3Bl usePosition = World.Transform(Entry.PerPixel().Position);

        // Create lighting by addition that we want to use on the color.
        DiffuseMaterialCl diffMaterial = new DiffuseMaterialCl() { 
          Ambient = { Knob = 1 }, 
          Knob = 1,
        };
        SpecularMaterialCl specMaterial = new SpecularMaterialCl() { Power = 3, Knob = 1, };
        // Light definitions borrowed from WPF 3D.
        AmbientLightBl ambLight = new AmbientLightBl() {
          Color = new ColorBl(0.1, 0.3, 0.5),
        };
        SpotLightBl spotLight = new SpotLightBl() {
          Direction = (new Double3Bl(0, 0, +1)).Normalize,
          Color = new ColorBl(.9d, 0, 0),
          Position = new Double3Bl(0, 0, -10),
          InnerConeAngle = 2.Degrees(),
          OuterConeAngle = 4.Degrees(),
        };
        DirectionalLightBl dirLight = new DirectionalLightBl() {
          Direction = (new Double3Bl(-.5, -.5, +.5)).Normalize,
          Color = new ColorBl(0.0, 0.7, 0.5),
          //Color = new ColorBl(1, 1, 1),
        };
        Lighting L = (diffMaterial + specMaterial).ApplyLights(ambLight + spotLight + dirLight);

        // Compute normal for lighting. Parametric surface normals are computed
        // via symbolic differentiation. Only transform by the world matrix.
        // Must cast to point 3D because lighting computations doesn't work on 4D points,
        // casting will incorporate the w component into the resulting 3D point. 
        Double3Bl normal = World.Transform(Entry.PerPixel().Normal) * new Double3Bl(1, 1, -1);
        // Compute position for lighting. Again only transform by world matrix.
        Double3Bl usePosition = World.Transform(Entry.PerPixel().Position);


        // Apply lighting, assign vertex color.
        // sample the Autumn texture using the current parametric point. The range of uv
        // is [-.5,-.5] to [+.5,+.5] while the range of a texture sample is [0,0] to [1,1].
        // As a result, we add a half a unit (0.5) to uv before sampling. 
        // Note that this computation will occur in the pixel shader where uv will be properly 
        // interpolated.
        vertex.Color = L.ApplyAt(usePosition, normal, Camera.Position).ForColor(Colors.White); //.ForPixel(Autumn, Entry.Texture);
      });
    }
  }
}
